#include "stdafx.h"
#include "d3d.h"
#include <cassert>
#include <algorithm>
//#include <cmath>
namespace d3d
{
#define SAMPLE_COUNT 2
	InputLayout* create_inputlayout(Device* device, Blob* vs, VertexType vertex_type)
	{

		D3D11_INPUT_ELEMENT_DESC static_desc[] = 
		{
			{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT , D3D11_INPUT_PER_VERTEX_DATA, 0}, 
			{"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT , D3D11_INPUT_PER_VERTEX_DATA, 0}, 
			{"TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT , D3D11_INPUT_PER_VERTEX_DATA, 0}, 
			{"BITANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT , D3D11_INPUT_PER_VERTEX_DATA, 0}, 
			{"UV", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT , D3D11_INPUT_PER_VERTEX_DATA, 0}, 
		};
		D3D11_INPUT_ELEMENT_DESC animated_desc[] = 
		{
			{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT , D3D11_INPUT_PER_VERTEX_DATA, 0}, 
			{"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT , D3D11_INPUT_PER_VERTEX_DATA, 0}, 
			{"TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT , D3D11_INPUT_PER_VERTEX_DATA, 0}, 
			{"BITANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT , D3D11_INPUT_PER_VERTEX_DATA, 0}, 
			{"UV", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT , D3D11_INPUT_PER_VERTEX_DATA, 0}, 
			{"BONES", 0, DXGI_FORMAT_R32G32_UINT, 0, D3D11_APPEND_ALIGNED_ELEMENT , D3D11_INPUT_PER_VERTEX_DATA, 0}, 
			{"BONEWEIGHTS", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT , D3D11_INPUT_PER_VERTEX_DATA, 0}, 
		};
		D3D11_INPUT_ELEMENT_DESC* desc;
		uint count = 0;
		if(vertex_type == eStatic)
		{
			desc = static_desc;
			count = 5;
		}
		else if (vertex_type == eAnimated)
		{
			desc = animated_desc;
			count = 7;
		}
		ID3D11InputLayout* layout;
		auto r = device->CreateInputLayout(desc, count, vs->GetBufferPointer(), 
			vs->GetBufferSize(), &layout);
		assert(!(FAILED(r)));
		return layout;
	}

	Blob* create_shaderblob(FilePath path, const char* func_name, const char* type)
	{
		ID3D10Blob* blob;
		ID3D10Blob* error;
		auto r = D3DX11CompileFromFile(path.c_str(), NULL, NULL, func_name, type, 
			0//D3DCOMPILE_DEBUG// | 
			//D3DCOMPILE_ENABLE_STRICTNESS | 
			//D3DCOMPILE_PREFER_FLOW_CONTROL |
			//D3DCOMPILE_SKIP_OPTIMIZATION
			, 
			0, NULL, 
			&blob, &error, NULL);
		if(FAILED(r))
		{
			auto error_string = (char*)error->GetBufferPointer();
			printf("%s\n", error_string);
		}
		assert(!FAILED(r));
		return blob;
	}

	VS* create_vs( Device* device, Blob* blob )
	{
		VS* vs;
		auto r = device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, &vs);
		assert(!(FAILED(r)));
		set_name(vs, "vs");
		return vs;
	}

	PS* create_ps( Device* device, Blob* blob )
	{
		PS* ps;
		auto r = device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, &ps);
		assert(!(FAILED(r)));
		set_name(ps, "ps");
		return ps;
	}

	VS* create_vs( Device* device, FilePath path )
	{
		return create_vs(device, create_shaderblob(path, "vs", "vs_5_0"));
	}

	PS* create_ps( Device* device, FilePath path )
	{
		return create_ps(device, create_shaderblob(path, "ps", "ps_5_0"));
	}


	Buffer* create_buffer( Device* device, const void* data_ptr, uint size, uint binding, D3D11_USAGE usage,
		D3D11_CPU_ACCESS_FLAG cpu_flag )
	{
		D3D11_BUFFER_DESC buf_desc;
		buf_desc.Usage = usage;
		buf_desc.ByteWidth = size;
		buf_desc.BindFlags = binding;
		buf_desc.CPUAccessFlags = cpu_flag;
		buf_desc.MiscFlags = 0;
		buf_desc.StructureByteStride = 0;

		D3D11_SUBRESOURCE_DATA subresc;
		subresc.pSysMem = data_ptr;
		subresc.SysMemPitch = 0;
		subresc.SysMemSlicePitch = 0;

		ID3D11Buffer* buffer;
		auto r = device->CreateBuffer(&buf_desc, &subresc, &buffer);
		assert(!(FAILED(r)));
		return buffer;
	}

	Buffer* create_buffer( Device* device, uint size, uint binding, D3D11_USAGE usage, D3D11_CPU_ACCESS_FLAG cpu_flag )
	{
		D3D11_BUFFER_DESC buf_desc;
		buf_desc.Usage = usage;
		buf_desc.ByteWidth = max(size, 16);
		buf_desc.BindFlags = binding;
		buf_desc.CPUAccessFlags = cpu_flag;
		buf_desc.MiscFlags = 0;
		buf_desc.StructureByteStride = 0;

		ID3D11Buffer* buffer;
		auto r = device->CreateBuffer(&buf_desc, nullptr, &buffer);
		assert(!(FAILED(r)));
		return buffer;
	}

	VertexBuffer create_vb( Device* device, shared_ptr<Geometry> data )
	{
		auto vb = create_buffer(device, data->vertices_pointer(), 
			data->vertices_byte_size(), D3D11_BIND_VERTEX_BUFFER, D3D11_USAGE_DEFAULT, (D3D11_CPU_ACCESS_FLAG)0);
		set_name(vb, "vertex buffer");

		VertexBuffer vertex_buffer;
		vertex_buffer.backing = vb;
		vertex_buffer.stride = data->m_vertex_stride;
		return vertex_buffer;
	}

	IndexBuffer create_ib( Device* device, shared_ptr<Geometry> data )
	{
		uint* ib_ptr = data->indexes_pointer();
		auto ib = create_buffer(device, ib_ptr, 
			data->indexes_byte_size(), D3D11_BIND_INDEX_BUFFER, D3D11_USAGE_DEFAULT, (D3D11_CPU_ACCESS_FLAG)0);
		set_name(ib, "index buffer");
		IndexBuffer index_buffer;
		index_buffer.backing = ib;
		index_buffer.indexes_count = data->m_indexes_count;
		return index_buffer;
	}
	Buffer* create_cb( Device* device, uint size )
	{
		auto cb = create_buffer(device, size, D3D11_BIND_CONSTANT_BUFFER, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE);
		set_name(cb, "constant buffer");
		return cb;
	}


	std::tuple<shared_ptr<DXDevice>, shared_ptr<DXContext>, SwapChain*> 
		initialize( HWND window, uint w, uint h )
	{
		DXGI_SWAP_CHAIN_DESC scd;

		// clear out the struct for use
		ZeroMemory(&scd, sizeof(scd));

		// fill the swap chain description struct

		scd.BufferCount = 2;                                    // one back buffer
		//GAMMA
		scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;     // use 32-bit color          // one back buffer
		scd.BufferDesc.Width = w;  
		scd.BufferDesc.Height = h;  
		scd.BufferDesc.RefreshRate.Numerator = 60;
		scd.BufferDesc.RefreshRate.Denominator = 1;
		scd.BufferDesc.Scaling = DXGI_MODE_SCALING_CENTERED;
		scd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;

		scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
		scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;      // how swap chain is to be used
		scd.OutputWindow = window;                                // the window to be used
		scd.SampleDesc.Count = 1;                               // how many multisamples
		scd.Windowed = TRUE;                                    // windowed/full-screen mode

		Device* device;
		SwapChain* swap_chain;
		Context* context;

		auto r = D3D11CreateDeviceAndSwapChain(NULL,
			D3D_DRIVER_TYPE_HARDWARE,
			NULL,
			D3D11_CREATE_DEVICE_DEBUG,
			NULL,
			NULL,
			D3D11_SDK_VERSION,
			&scd,
			&swap_chain,
			&device,
			NULL,
			&context);
		
		return std::tuple<shared_ptr<DXDevice>, shared_ptr<DXContext>, SwapChain*>(
			shared_ptr<DXDevice>(new DXDevice(unique_ptr<Device>(device))), 
			shared_ptr<DXContext>(new DXContext(unique_ptr<Context>(context))), 
			swap_chain);
	}

	DepthStencilView* create_dsv( Device* device, Texture2D* tex2d )
	{
		DepthStencilView* dsv;
		D3D11_DEPTH_STENCIL_VIEW_DESC desc;
		ZeroMemory(&desc, sizeof(decltype(desc)));
		desc.Format = DXGI_FORMAT_D32_FLOAT;		
		desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMS;
		device->CreateDepthStencilView(tex2d, &desc, &dsv);
		return dsv;
	}

	RenderTargetView* create_rtv( Device* device, SwapChain* swapchain )
	{
		ID3D11Texture2D* tex;
		swapchain->GetBuffer(0, __uuidof(tex), (void**)&tex);
		auto rtv = create_rtv(device, tex);
		tex->Release();
		return rtv;
	}

	RenderTargetView* create_rtv( Device* device, Texture2D* tex2d )
	{
		RenderTargetView* rtv;
		auto r = device->CreateRenderTargetView(tex2d, nullptr, &rtv);
		assert(!FAILED(r));
		return rtv;
	}
	ShaderResourceView* create_srv( Device* device, Texture2D* tex2d )
	{
		ShaderResourceView* srv;
		auto r = device->CreateShaderResourceView(tex2d, nullptr, &srv);
		assert(!FAILED(r));
		return srv;
	}

	ShaderResourceView* create_srv( Device* device, Texture2D* tex2d, DXGI_FORMAT format, D3D11_SRV_DIMENSION dimension )
	{
		D3D11_SHADER_RESOURCE_VIEW_DESC desc;
		ShaderResourceView* srv;
		ZeroMemory(&desc, sizeof(decltype(desc)));
		desc.ViewDimension = dimension;
		desc.Format = format;
		desc.Texture2D.MipLevels = -1;
		auto r = device->CreateShaderResourceView(tex2d, &desc, &srv);
		assert(!FAILED(r));
		return srv;
	}


	ViewPort create_viewport( uint width, uint height )
	{
		ViewPort viewport;
		viewport.Width = width;
		viewport.Height = height;
		viewport.MinDepth = 0;
		viewport.MaxDepth = 1;
		viewport.TopLeftX = 0;
		viewport.TopLeftY = 0;
		return viewport;
	}

	void unlock_buffer(Context* context, Buffer* buffer)
	{
		context->Unmap(buffer, 0);
	}

	Texture2D* create_texture2d( Device* device, uint w, uint h, DXGI_FORMAT format,  uint binding )
	{
		D3D11_TEXTURE2D_DESC desc;
		ZeroMemory(&desc, sizeof(D3D11_TEXTURE2D_DESC));
		desc.BindFlags = binding;
		desc.Width = w;
		desc.Height = h;
		desc.CPUAccessFlags = 0;
		desc.Usage = D3D11_USAGE_DEFAULT;
		desc.SampleDesc.Count = SAMPLE_COUNT;
		desc.MipLevels = 1;
		desc.Format = format;
		desc.ArraySize = 1;
		Texture2D* tex2d;
		auto r = device->CreateTexture2D(&desc, nullptr, &tex2d);
		assert(!FAILED(r));
		return tex2d;
	}

	Texture2D* create_depth_texture( Device* device, uint w, uint h, uint binding )
	{
		return create_texture2d(device, w, h, DXGI_FORMAT_D32_FLOAT, binding);
	}

	void set_buffers( Context* context, uint vb_stride, Buffer* vb, Buffer* ib, D3D11_PRIMITIVE_TOPOLOGY topology )
	{
		uint offset = 0;
		context->IASetVertexBuffers(0, 1, &vb, &vb_stride, &offset);
		context->IASetIndexBuffer(ib, DXGI_FORMAT_R32_UINT, 0);
		context->IASetPrimitiveTopology(topology);
	}
	void set_vs_cb(Context* context, Buffer* const* vs_cb, uint vs_cb_count)
	{
		for(uint i = 0; i < vs_cb_count; i++)
		{
			context->VSSetConstantBuffers(i, 1, &vs_cb[i]);
		}
	}
	void set_ps_cb(Context* context, Buffer* const* ps_cb, uint ps_cb_count)
	{
		for(uint i = 0; i < ps_cb_count; i++)
		{
			context->PSSetConstantBuffers(i, 1, &ps_cb[i]);
		}
	}
	void draw( Context* context, VertexBuffer& vertex_buffer, IndexBuffer& index_buffer,
		VS* vs, PS* ps, InputLayout* input_layout,
		Buffer* const* vs_cb, uint vs_cb_count, Buffer* const* ps_cb, uint ps_cb_count)
	{
		set_buffers(context, vertex_buffer.stride, vertex_buffer.backing, index_buffer.backing, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

		set_vs_cb(context, vs_cb, vs_cb_count);
		set_ps_cb(context, ps_cb, ps_cb_count);

		context->IASetInputLayout(input_layout);
		context->VSSetShader(vs, nullptr, 0);
		context->PSSetShader(ps, nullptr, 0);
		context->DrawIndexed(index_buffer.indexes_count, 0, 0);
	}

	void draw( Context* context, VertexBuffer& vertex_buffer, IndexBuffer& index_buffer,
		Shader shader, Buffer* const* vs_cb, uint vs_cb_count, Buffer* const* ps_cb, uint ps_cb_count )
	{
		draw(context, vertex_buffer, index_buffer, shader.vs, 
			shader.ps, shader.input_layout, vs_cb, vs_cb_count, ps_cb, ps_cb_count);
	}

	void set_viewport( Context* context, uint width, uint height )
	{
		auto vp = create_viewport(width, height);
		context->RSSetViewports(1, &vp);
	}

	Shader create_shader_and_input_layout( Device* device, FilePath path, VertexType vertex_type, const char* vs_func_name )
	{
		Shader shader;
		auto vs_blob = d3d::create_shaderblob(path, vs_func_name, "vs_5_0");
		shader.vs = d3d::create_vs(device, vs_blob);
		shader.ps = d3d::create_ps(device, path);
		shader.input_layout = d3d::create_inputlayout(device, vs_blob, vertex_type);

		return shader;		
	}

	SamplerState* create_sampler_state(Device* device, D3D11_FILTER filter )
	{
		SamplerState* sampler_state;
		D3D11_SAMPLER_DESC samplerDesc;
		samplerDesc.Filter = filter;
		samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
		samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
		samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
		samplerDesc.MipLODBias = 0;
		samplerDesc.MaxAnisotropy = 16;
		samplerDesc.BorderColor[0] = 0;
		samplerDesc.BorderColor[1] = 0;
		samplerDesc.BorderColor[2] = 0;
		samplerDesc.BorderColor[3] = 0;
		samplerDesc.MinLOD = 0;
		samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;
		device->CreateSamplerState(&samplerDesc, &sampler_state);
		return sampler_state;
	}

	void clear( Context* context, RenderTargetView* view, float4 value )
	{
		context->ClearRenderTargetView(view, (float*)&value);
	}

	void clear( Context* context, DepthStencilView* view, float value )
	{
		context->ClearDepthStencilView(view, D3D11_CLEAR_DEPTH, value, 0);
	}

	void resize(SwapChain* swap_chain, Device* device, Size desired_size, RenderTargetView** rtv)
	{
		(*rtv)->Release();
		//GAMMA
		swap_chain->ResizeBuffers(2, desired_size.w, desired_size.h, DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, 0);
		*rtv = create_rtv(device, swap_chain);
	}
	void resize(Device* device, Size desired_size, Texture2D** tex2d)
	{
		assert(*tex2d != nullptr);
		D3D11_TEXTURE2D_DESC desc;
		desc.Width = desired_size.w;
		desc.Height = desired_size.h;
		(*tex2d)->GetDesc(&desc);
		(*tex2d)->Release();
		device->CreateTexture2D(&desc, nullptr, tex2d);
	}

	ShaderResourceView* load_texture( Device* device, FilePath path, DXGI_FORMAT desired_format )
	{
		Texture2D* texture2d;
		D3DX11_IMAGE_INFO src_ili;
		auto r = D3DX11GetImageInfoFromFile(path.c_str(), nullptr, &src_ili, nullptr );

		if(SUCCEEDED(r))
		{

			D3DX11_IMAGE_LOAD_INFO ili;

			ZeroMemory(&ili, sizeof(D3DX11_IMAGE_LOAD_INFO ));
			ili.Width = src_ili.Width;
			ili.Height = src_ili.Height;
			ili.Usage = D3D11_USAGE_DEFAULT;
			ili.BindFlags = D3D11_BIND_SHADER_RESOURCE;
			ili.CpuAccessFlags = 0;
			ili.MipLevels = D3DX11_DEFAULT ;
			ili.Format = desired_format;
			ili.pSrcInfo = &src_ili;


			r = D3DX11CreateTextureFromFile(device, path.c_str(), &ili, nullptr, (ID3D11Resource**)&texture2d, nullptr);

			auto texture_srv = d3d::create_srv(device, texture2d, desired_format, D3D11_SRV_DIMENSION_TEXTURE2D);

			d3d::set_name(texture2d, "image texture");
			texture2d->Release();
			return texture_srv;
		}
		return nullptr;
	}


	DXDevice::DXDevice( unique_ptr<ID3D11Device> device ) : m_device(move(device))
	{

	}

	DXDevice::~DXDevice()
	{
		m_device->Release(); 
		m_device = nullptr;
	}


	DXContext::DXContext( unique_ptr<ID3D11DeviceContext> context ) : m_context(move(context))
	{

	}

	DXContext::~DXContext()
	{
		m_context->Release(); 
		m_context = nullptr;
	}

}